<template>
  <div class="wrap">
    <div class="zoom-btn">
      <span @click="getMapTarget">当前地图容器：{{ mapDiv }}</span>
      当前缩放级别：{{ mapZoom }} 当前分辨率：{{ mapResolution }}
      <button @click="handZoom('max')">放大</button>
      <button @click="handZoom('min')">缩小</button>
      <button @click="handRotate('left')">↻</button>
      <button @click="handRotate('right')">↺</button>
      <button @click="toCity">随机动画切换中心点</button>
      <button @click="initZoom">重置</button>
    </div>
    <div class="zoom-btn" style="top: 60px">
      <button @click="handleAddClick">添加点击</button>
      <button @click="handleLayers">切换图层</button>
      <button @click="handleAddLayer">添加/隐藏图片标记</button>
      <button @click="handleAddLayerColor">添加/隐藏带颜色标记</button>
      <button @click="handlePolymerization">添加/隐藏聚合数据</button>
      <button @click="handleDraw('Point')">画点</button>
      <button @click="handleClear('Point')">清除点</button>
      <button @click="handleDraw('LineString')">画线</button>
      <button @click="handleDraw('LineString', true)">自由画线</button>
      <button @click="handleClear('LineString')">清除线</button>
      <button @click="handleDraw('Polygon')">画多边形</button>
      <button @click="handleDraw('Polygon', true)">自由画多边形</button>
      <button @click="handleClear('Polygon')">清除多边形</button>
    </div>
    <div class="zoom-btn" style="top: 100px">
      <button @click="handleDraw('Circle')">画圆</button>
      <button @click="handleClear('Circle')">清除圆</button>
      <button @click="handleDraw('Square')">画方形</button>
      <button @click="handleClear('Square')">清除方形</button>
      <button @click="handleDraw('Rectangle')">画矩形</button>
      <button @click="handleClear('Rectangle')">清除矩形</button>
      <button @click="handleDraw('Hexagram')">画六芒星</button>
      <button @click="handleClear('Hexagram')">清除六芒星</button>
      <button @click="handleDraw('LineString', false, 'arrow')">
        画带箭头线段
      </button>
      <button @click="handleDrawLineForPoint">按坐标点显示/隐藏线段</button>
      <button @click="handleDrawPolygonForPoint">按坐标点显示/隐藏区域</button>
      <button @click="handleMoveForPoint">按轨迹移动/隐藏</button>
    </div>
    <!--创建地图容器 设置了tabindex="2" 表示页面加载时地图未激活状态，无法对地图进行缩放-->
    <div id="mapContent" class="map-content" tabindex="2"></div>
    <!-- 弹窗叠加层 -->
    <div ref="popup" class="popup">
      <span class="icon-close" @click="closePopup">✖</span>
      <div class="content">{{ currentCoordinate }}</div>
    </div>
    <!-- 添加标记 -->
    <span class="vienna" ref="viennaTxt">故宫</span>
    <div class="marker" title="Marker" ref="marker"></div>
    <span class="vienna" ref="viennaTxt1">其他</span>
    <div class="marker" title="Marker" ref="marker1"></div>
    <!-- 展示鼠标坐标 -->
    <div class="mousePosition" ref="mousePositionTxt"></div>
  </div>
</template>

<script>
import "ol/ol.css";
import { Map, View } from "ol";
import Tile from "ol/layer/Tile";
import OSM from "ol/source/OSM";
import XYZ from "ol/source/XYZ"; // 引入XYZ地图格式
import Overlay from "ol/Overlay.js"; // 叠加层
import { toStringHDMS } from "ol/coordinate"; // 获取坐标
import { toLonLat } from "ol/proj"; // 获取坐标
import { fromLonLat, transform } from "ol/proj"; // 通过坐标得到地图中位置 ,reansform用来对坐标类型进行转换
import DragRotateAndZoom from "ol/interaction/DragRotateAndZoom"; // 按下shift旋转
import * as olInteraction from "ol/interaction"; // 按下shift旋转
import * as olEasing from "ol/easing"; // 动画
import Graticule from "ol/layer/Graticule"; // 添加网格
import Stroke from "ol/style/Stroke"; // 添加网格
import * as control from "ol/control"; // 添加控件
import * as coordinate from "ol/coordinate"; // 鼠标坐标控件
import Point from "ol/geom/Point"; // 添加图层
import { Feature } from "ol"; // 添加图层
import LayerVector from "ol/layer/Vector"; // 添加图层
import SourceVector from "ol/source/Vector"; // 添加图层
import { Style, RegularShape, Fill, Icon } from "ol/style"; // 添加图层
import * as Utils from "./utils";
import * as ol from "ol";
export default {
  name: "myOpenlayersStudy",
  data() {
    return {
      map: null, // 地图
      overlay: null, // 弹框叠加层
      currentCoordinate: "", // 弹框叠加层显示的坐标
      markText: null, // 标签文字
      markerDiv: null, // 标签div
      mapZoom: 12,
      mapDiv: "", // 地图容器
      mapResolution: 0.00034332275390625, // 分辨率
      lineLayer: null,
      index: 0,
      carPoint: null,
      feature: null,
    };
  },
  //   一定要在mounted中dom渲染之后初始化地图
  mounted() {
    this.initMap();
  },
  methods: {
    initMap() {
      /**
       * map: 地图容器，核心部分，可加载各类地图于功能控件，用于渲染、表现动态地图
       * target:目标元素。即地图容器元素
       * layers:图层，包含多个调用数据的子类，由子类的实例加载地图数据，必须结合图层数据源匹配使用
       * views:地图视图，控制地图缩放等基本交互，以及地图投影坐标系，地图中心点，分辨率。旋转角度等
       */
      this.map = new Map({
        target: "mapContent", // 对应页面里 id 为 map 的元素
        interactions: olInteraction
          .defaults()
          .extend([new DragRotateAndZoom()]), //按下shift旋转
        // 添加比例尺控件
        controls: control.defaults().extend([
          new control.ScaleLine(), // 比例尺
          // new control.FullScreen(), // 全屏控件
          new control.MousePosition({
            coordinateFormat: coordinate.createStringXY(4),
            projection: "EPSG:4326",
            target: this.$refs.mousePositionTxt,
          }),
        ]),
        layers: [
          // 图层
          new Tile({
            name: "defaultLayer",
            source: new XYZ({
              url: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}",
            }),
          }),
        ],
        view: new View({
          // 地图视图
          projection: "EPSG:4326", // 坐标系，有EPSG:4326和EPSG:3857
          center: [116.39714863718898, 39.913172009659036], // 北京坐标
          minZoom: 10, // 地图缩放最小级别
          zoom: this.mapZoom, // 地图缩放级别（打开页面时默认级别）
        }),
      });
      /**
       * 叠加层弹框
       * overlay：叠加层，即叠加到地图上显示的元素，关联了一个自定义的HTML元素，由一个单一的地图坐标点确定叠加位置。与控件类似，但不同的是叠加元素不是在一个固定的屏幕位置上，而是通过关联一个地图逻辑坐标点跟随地图移动，如标注点、popup等。
       */
      this.overlay = new Overlay({
        element: this.$refs.popup, // 弹窗标签，在html里
        autoPan: true, // 如果弹窗在底图边缘时，底图会移动
        autoPanAnimation: {
          // 底图移动动画
          duration: 250,
        },
      });
      this.map.addOverlay(this.overlay);

      /**
       * 添加标记
       * 文字标签
       * div标签
       */
      let pos = fromLonLat(
        transform(
          [116.39714863718898, 39.913172009659036],
          "EPSG:3857",
          "EPSG:4326"
        )
      ); // 故宫坐标
      this.markText = new Overlay({
        position: pos,
        element: this.$refs.viennaTxt,
      });
      this.markerDiv = new Overlay({
        position: pos,
        positioning: "center-center", // 如果不配置，则圆的左上角和坐标点对其
        element: this.$refs.marker,
        stopEvent: false, // 不阻止默认事件，比如鼠标放到圆点上时，滚动鼠标滚轮，也可以缩放地图
      });
      this.map.addOverlay(this.markText);
      this.map.addOverlay(this.markerDiv);
      var pos1 = fromLonLat(
        transform(
          [117.12871618857545, 39.82053606071721],
          "EPSG:3857",
          "EPSG:4326"
        )
      ); // 故宫坐标
      this.markText = new Overlay({
        position: pos1,
        element: this.$refs.viennaTxt1,
      });
      this.markerDiv = new Overlay({
        position: pos1,
        positioning: "center-center", // 如果不配置，则圆的左上角和坐标点对其
        element: this.$refs.marker1,
        stopEvent: false, // 不阻止默认事件，比如鼠标放到圆点上时，滚动鼠标滚轮，也可以缩放地图
      });
      this.map.addOverlay(this.markText);
      this.map.addOverlay(this.markerDiv);

      //   //   监听鼠标滚动缩放级别
      this.map.getView().on("change:resolution", () => {
        this.mapZoom = this.map.getView().getZoom(); //获取当前地图的缩放级别
        this.mapResolution = this.map.getView().getResolution(); // 获取当前地图分辨率
      });

      // 添加网格
      let graticule = new Graticule({
        strokeStyle: new Stroke({
          color: "rgba(255, 120, 0, .5)", // 线条颜色
          width: 1, // 线条宽度
          lineDash: [4], // 虚线，每隔4像素
        }),
        showLabels: true,
      });
      graticule.setMap(this.map);

      // 设置比例尺单位
      new control.ScaleLine().setUnits("metric");

      // 添加图层并设置层级
      var point = new Point([116.29527619094068, 39.965631727503656]);
      var feature = new Feature(point);
      feature.setStyle(
        new Style({
          image: new RegularShape({
            fill: new Fill({ color: "green" }),
            stroke: new Stroke({ color: "black", width: 1 }),
            points: 5,
            radius: 20,
            radius2: 4,
            angle: 0,
          }),
        })
      );
      var source = new SourceVector({
        features: [feature],
      });
      var vectorLayer = new LayerVector({
        source: source,
      });
      vectorLayer.setZIndex(5);
      this.map.addLayer(vectorLayer); // 添加图层
      var point = new Point([116.29912140997538, 39.964533095738894]);
      var feature = new Feature(point);
      feature.setStyle(
        new Style({
          image: new RegularShape({
            fill: new Fill({ color: "red" }),
            stroke: new Stroke({ color: "black", width: 1 }),
            points: 3,
            radius: 20,
            rotation: Math.PI / 4,
            angle: 0,
          }),
        })
      );
      var source = new SourceVector({
        features: [feature],
      });
      var vectorLayer = new LayerVector({
        source: source,
        maxResolution: 0.0003,
        minResolution: 0.0000095,
      });
      vectorLayer.setZIndex(4);
      this.map.addLayer(vectorLayer);
    },
    // 关闭弹窗叠加层
    closePopup() {
      this.overlay.setPosition(undefined);
      this.currentCoordinate = null;
    },
    // 地图缩放
    handZoom(val) {
      let view = this.map.getView(); // 获取当前视图
      let zoom = view.getZoom(); // 获取当前缩放级别
      this.mapZoom = val == "max" ? zoom + 1 : zoom - 1;
      view.setZoom(this.mapZoom);
    },
    // 点击恢复初始化
    initZoom() {
      this.map.getView().setZoom(12);
      this.mapZoom = 12;
      this.map.getView().setRotation(0);
      this.map.getView().animate({
        center: [116.39714863718898, 39.913172009659036], // 初始位置
        duration: 2000, // 动画时长
      });
      this.map
        .getLayers()
        .item(0)
        .setSource(
          new XYZ({
            url: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}",
          }) // 图层数据源
        );
    },
    // 获取地图容器
    getMapTarget() {
      this.mapDiv = this.map.getTarget(); // 获取地图容器id
      // setTarget 设置地图容器
    },
    // 地图旋转
    handRotate(val) {
      let currentRotation = this.map.getView().getRotation();
      var rote = val == "left" ? currentRotation + 1 : currentRotation - 1;
      this.map.getView().setRotation(rote);
    },
    // 切换中心点
    toCity() {
      var view = this.map.getView();
      var num = Math.floor(Math.random() * 7);
      var mycenter =
        view.getCenter()[0] == "116.39714863718898"
          ? [117.12871618857545, 39.82053606071721]
          : [116.39714863718898, 39.913172009659036];
      switch (num) {
        case 0:
          view.animate({
            center: mycenter, // 目标位置
            duration: 2000, // 动画时长
            easing: olEasing.easeOut, // 动画: 传入动画函数，olEasing是内置动画集
          });
          break;
        case 1:
          view.animate({
            center: mycenter, // 目标位置
            duration: 2000, // 动画时长
            easing: Utils.bounce, // 动画：传入动画函数
          });
          break;
        case 2:
          //   动画组合
          view.animate(
            {
              // 将多个动画连在一起使用
              center: [
                view.getCenter()[0] + (mycenter[0] - view.getCenter()[0]) / 2,
                view.getCenter()[1] + (mycenter[1] - view.getCenter()[1]) / 2,
              ],
              rotation: Math.PI,
              easing: olEasing.easeIn,
            },
            {
              center: mycenter,
              rotation: 2 * Math.PI, // 旋转角度
              easing: olEasing.easeOut,
            }
          );
          break;
        case 3:
          //   绕一点旋转
          view.animate(
            {
              // 将多个动画连在一起使用
              rotation: view.getRotation() + Math.PI,
              anchor: mycenter, // 锚点
              easing: olEasing.easeIn, // 动画：传入动画函数
            },
            {
              rotation: view.getRotation() + 2 * Math.PI,
              anchor: mycenter, // 锚点
              easing: olEasing.easeOut, // 动画：传入动画函数
            }
          );
          break;

        case 4:
          // 飞行动画
          Utils.flyTo(mycenter);
          break;
        default:
          view.animate({
            center: mycenter, // 目标位置
            duration: 2000, // 动画时长
          });
          break;
      }
    },
    // 添加点击事件
    handleAddClick() {
      // 给地图添加单击事件 ，单击后显示弹框（含坐标信息）
      this.map.on("singleclick", (evt) => {
        const coordinate = evt.coordinate; // 获取坐标
        const hdms = toStringHDMS(toLonLat(coordinate)); // 转换坐标格式
        this.currentCoordinate = coordinate; // 保存坐标点
        this.overlay.setPosition(coordinate);
      });
    },
    // 切换图层
    handleLayers() {
      Number(this.map.getLayers().item(0).revision_) % 2 == 1
        ? this.map.getLayers().item(0).setSource(
            new OSM() // 图层数据源
          )
        : this.map
            .getLayers()
            .item(0)
            .setSource(
              new XYZ({
                url: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}",
              }) // 图层数据源
            );
    },
    // 添加隐藏图片标记
    handleAddLayer() {
      var layer = Utils.getLayerFromName(this.map,"imgLayer");
      if (layer) {
        this.map.removeLayer(layer);
        return false;
      }
      var iconvectorLayer = Utils.getIconvectorLayer([
        116.29335359190071, 39.858515023046216,
      ]);
      this.map.addLayer(iconvectorLayer);
    },
    // 添加带颜色的标记
    handleAddLayerColor() {
      var layer = Utils.getLayerFromName(this.map,"colorLayer");
      if (layer) {
        this.map.removeLayer(layer);
        return false;
      }
      var vectorLayer = Utils.getColorIconFeatureLayer([116.44826082265416, 39.971124891566156])
      console.log("vectorLayer", vectorLayer);
      this.map.addLayer(vectorLayer);
    },
    // 添加聚合图层
    handlePolymerization() {
      for (let i = 0; i < this.map.getLayers().getProperties().length; i++) {
        if (
          this.map.getLayers().item(i).getProperties().name == "polymerization"
        ) {
          this.map.removeLayer(this.map.getLayers().item(i));
          return;
        }
      }
      var layer = Utils.getPolymerizatioLayer();
      this.map.addLayer(layer);
    },
    // 画点线面
    handleDraw(val, isFreehand, isArrow) {
      var { vector, draw } = Utils.DrawFn(val, isFreehand, isArrow);
      this[`draw${val}`] = draw;
      this[`vector${val}`] = vector;
      this.map.addLayer(vector);
      this.map.addInteraction(draw);
    },
    // 清除点线面
    handleClear(val) {
      this.map.removeLayer(this[`vector${val}`]);
      this.map.removeInteraction(this[`draw${val}`]);
    },
    // 根据坐标点画线
    handleDrawLineForPoint() {
      var layer = Utils.getLayerFromName(this.map,"drawLineFromPoint");
      if (layer) {
        this.map.removeLayer(layer);
        return false;
      }
      // 获取点
      var point = [
        [116.29449513377102, 39.9657003910067],
        [116.30018499562786, 39.900492734165454],
        [116.34989138437736, 39.9078879670125],
        [116.39669981958322, 39.913663033693744],
        [116.42755083369617, 39.90393660559902],
        [116.40096375055495, 39.86833580634884],
      ];
      // 获取图层
      var layer = Utils.drawLineFromPoint(point);
      this.map.addLayer(layer);
    },
    // 根据坐标点画区域
    handleDrawPolygonForPoint() {
      var layer = Utils.getLayerFromName(this.map,"drawPolygonFromPoint");
      if (layer) {
        this.map.removeLayer(layer);
        return false;
      }
      var point = [
        [116.48679443903151, 39.934597097852745],
        [116.48370453424636, 39.89580162666134],
        [116.55099579401198, 39.89408501289181],
        [116.55339905328933, 39.94112023017696],
        [116.48679443903151, 39.934597097852745],
      ];
      var layer = Utils.drawPolygonFromPoint(point);
      console.log("layer", layer);
      this.map.addLayer(layer);
    },
    // 根据坐标点生成轨迹并图标移动
    handleMoveForPoint() {
      var layer = Utils.getLayerFromName(this.map,"drawLineFromPoint");
      if (layer) {
        this.map.removeLayer(layer);
        return false;
      }
      var point = [
        [116.29924530607833, 39.84736016255788],
        [116.34890682699792, 39.800585939366165],
        [116.3962585097352, 39.851402379376914],
        [116.44880732838267, 39.80116339891175],
        [116.50077868748457, 39.85024746028576],
        [116.5989468102326, 39.85082491983133],
        [116.64860833115219, 39.94899304257936],
      ];
      var pointList = point.map((item) => {
        return transform(item, "EPSG:3857", "EPSG:4326");
      });
      // 添加轨迹
      this.lineLayer = Utils.drawLineFromPoint(point);
      this.map.addLayer(this.lineLayer);
      // 起始点添加图标
      this.feature = Utils.getIconFeature(pointList[0]);
      this.lineLayer.getSource().addFeature(this.feature);
      this.index = 0;
      this.carPoint = JSON.parse(JSON.stringify(pointList));
      this.timeStart();
    },
    //计时器开始
    timeStart() {
      this.timer = setInterval(() => {
        if (this.index + 1 >= this.carPoint.length) {
          //重头开始
          this.index = 0;
          //移除要素
          this.lineLayer.getSource().removeFeature(this.feature);
          clearInterval(this.timer);
          //重复运动
          this.handleMoveForPoint(); //自动开启功能
          return;
        }
        //到转折点旋转角度
        if (this.nextPoint() === this.carPoint[this.index + 1]) {
          this.index++;
          this.feature.getStyle().getImage().setRotation(this.countRotate());
        }
        //改变坐标点
        this.feature
          .getGeometry()
          .setCoordinates(fromLonLat(this.carPoint[this.index]));
      }, 50);
    },
    //计算下一个点的位置
    //这里的算法是计算了两点之间的点   两点之间的连线可能存在很多个计算出来的点
    nextPoint() {
      let index = this.index;
      let p1 = this.map.getPixelFromCoordinate(
        fromLonLat(this.carPoint[index])
      ); //获取在屏幕的像素位置
      let p2 = this.map.getPixelFromCoordinate(
        fromLonLat(this.carPoint[index + 1])
      );
      let dx = p2[0] - p1[0];
      let dy = p2[1] - p1[1];
      //打印可见  在没有走到下一个点之前，下一个点是不变的，前一个点以这个点为终点向其靠近
      let distance = Math.sqrt(dx * dx + dy * dy);
      if (distance <= 1) {
        return this.carPoint[index + 1];
      } else {
        let x = p1[0] + dx / distance;
        let y = p1[1] + dy / distance;
        let coor = transform(
          this.map.getCoordinateFromPixel([x, y]),
          "EPSG:3857",
          "EPSG:4326"
        );
        this.carPoint[index] = coor; //这里会将前一个点重新赋值  要素利用这个坐标变化进行移动
        return this.carPoint[index];
      }
    },
    //计算两点之间的角度  算旋转角度
    countRotate() {
      let i = this.index,
        j = i + 1;
      if (j === this.carPoint.length) {
        i--;
        j--;
      }
      let p1 = this.carPoint[i];
      let p2 = this.carPoint[j];
      return Math.atan2(p2[0] - p1[0], p2[1] - p1[1]);
    },
  },
};
</script>

<style scoped lang="scss">
.wrap {
  width: 100%;
  height: 100%;
}
.zoom-btn {
  position: absolute;
  top: 20px;
  width: 85%;
  color: aqua;
  z-index: 9;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 0 100px;
  margin: 0 auto;
  text-align: center;
}
.map-content {
  margin: 0 auto;
  width: 100%;
  height: 100%;
}
.popup {
  width: 300px;
  height: 100px;
  background: #fff;
  position: absolute;
  top: -115px;
  left: -150px;
  box-sizing: border-box;
  padding: 10px;

  &::after {
    content: "";
    display: block;
    position: absolute;
    width: 20px;
    height: 20px;
    background: #fff;
    bottom: -10px;
    left: 50%;
    transform: translateX(-50%) rotate(45deg);
  }

  .icon-close {
    position: absolute;
    top: 0px;
    right: 8px;
  }

  .content {
    margin-top: 14px;
  }
}
// 标记
.marker {
  width: 20px;
  height: 20px;
  border: 1px solid #088;
  border-radius: 10px;
  background-color: #0ff;
  opacity: 0.5;
}
.vienna {
  text-decoration: none;
  color: white;
  font-size: 11pt;
  font-weight: bold;
  text-shadow: black 0.1em 0.1em 0.2em;
}
// 鼠标坐标控件展示
.mousePosition {
  color: aqua;
  position: absolute;
  width: 200px;
  height: 40px;
  bottom: 15px;
  right: 80px;
}
</style>
